﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Test30.Core;
using Test30.Data;
using Test30.Engine;
using Test30.Util;

namespace Test30.Tools.Reports
{
        public class AsyncStats
    {
        public event EventHandler Updated;
        public Content Content { get; }
        public Dictionary<Race, RaceStat> Races { get; }

        public bool MaxPerLevel = false;

        public int MaxRaceCount;
        public int MaxItemCount;
        public int MaxPowerCount;

        public int[] MaxRaceCounts = new int[100];
        public int[] MaxItemCounts = new int[100];
        public int[] MaxPowerCounts = new int[100];

        public IEnumerable<IStatRow> StatRows
        {
            get
            {
                foreach (RaceStat stat in Races.Values)
                {
                    yield return stat;
                }

//                foreach (ItemStat stat in _items.Values)
//                {
//                    yield return stat;
//                }
//
//                foreach (PowerStat stat in _powers.Values)
//                {
//                    yield return stat;
//                }
//
//                yield return _monsterHealth;
            }
        }

        public AsyncStats()
        {
            Content = DataFiles.Load();
            Races = new Dictionary<Race, RaceStat>();

            foreach (Race race in Content.Races)
            {
                Races[race] = new RaceStat(this, race);
            }

            foreach (ItemType type in Content.Items)
            {
                _items[type] = new ItemStat(this, type);
            }

            foreach (PowerType type in Content.Powers)
            {
                _powers[type] = new PowerStat(this, type);
            }

            _monsterHealth = new AverageStat("Monster health");
        }

        public void Run()
        {
            if (_state == RunState.NotStarted)
            {
                ThreadStart start = GenerateStats;
                Thread thread = new Thread(start) {Priority = ThreadPriority.AboveNormal};

                _state = RunState.Running;
                thread.Start();
            }
        }

        public void Stop()
        {
            _state = RunState.Stopped;
        }

        private void GenerateStats()
        {
            int generated = 0;
            //while (_state == RunState.Running)
            for (int k = 0; k < 1; k++)
            {
                for (int level = 1; level <= 100; level++)
                {
                    //### bob: slightly less accurate but faster way to generate
                    for (int i = 0; i < 5000; i++)
                    {
                        // let the level wander
                        int monsterLevel = Rng.WalkLevel(level).Clamp(1, 100);

                        // pick a race
                        Race race = Content.Races.RandomSoft(monsterLevel);

                        int count = Races[race].Increment(level);
                        MaxRaceCount = Math.Max(MaxRaceCount, count);
                        MaxRaceCounts[level - 1] = Math.Max(MaxRaceCounts[level - 1], count);

                        if (race.Drop != null)
                        {
                            foreach (var item in race.Drop.Create(race.Depth))
                            {
                                int itemCount = _items[item.Type].Increment(level);
                                MaxItemCount = Math.Max(MaxItemCount, itemCount);
                                MaxItemCounts[level - 1] = Math.Max(MaxItemCounts[level - 1], itemCount);

                                if (item.Power != null)
                                {
                                    int powerCount = _powers[item.Power.Type].Increment(level);
                                    MaxPowerCount = Math.Max(MaxPowerCount, powerCount);
                                    MaxPowerCounts[level - 1] = Math.Max(MaxPowerCounts[level - 1], powerCount);
                                }
                            }
                        }
                    }

                    // bail if stopped
                    if (_state != RunState.Running) break;

                    if (level % 10 == 0)
                    {
                        Updated?.Invoke(this, EventArgs.Empty);
                    }

                    //### bob: slower, more accurate way
                    /*
                    // generate actual dungeons
                    Game game = new Game(Hero.CreateTemp(), _content);
                    game.SetFloor(level);

                    Dictionary<Race, int> groupCounts = new Dictionary<Race, int>();

                    int numMonsters = 0;
                    int totalHealth = 0;
                    foreach (Monster monster in game.Dungeon.Entities.OfType<Monster>())
                    {
                        if (monster.Race.NumberInGroup > 1)
                        {
                            if (!groupCounts.ContainsKey(monster.Race))
                            {
                                groupCounts[monster.Race] = monster.Race.NumberInGroup;
                            }
                            else
                            {
                                groupCounts[monster.Race]--;
                                if (groupCounts[monster.Race] <= 0)
                                {
                                    groupCounts.Remove(monster.Race);
                                }
                                else
                                {
                                    // skip the monster to only count the group as one monster on average
                                    continue;
                                }
                            }
                        }

                        int count = _races[monster.Race].Increment(level);
                        MaxRaceCount = Math.Max(MaxRaceCount, count);
                        MaxRaceCounts[level - 1] = Math.Max(MaxRaceCounts[level - 1], count);

                        numMonsters++;
                        totalHealth += monster.Health.Current;
                    }

                    //### bob: monster health is assumed to increase logarithmically
                    _monsterHealth.Add(level, (int)(100.0f * Math.Log10(totalHealth) / numMonsters));

                    foreach (Item item in game.Dungeon.Items)
                    {
                        int count = _items[item.Type].Increment(level);
                        MaxItemCount = Math.Max(MaxItemCount, count);
                        MaxIte_counts[level - 1] = Math.Max(MaxIte_counts[level - 1], count);

                        if (item.Power != null)
                        {
                            int powerCount = _powers[item.Power.Type].Increment(level);
                            MaxPowerCount = Math.Max(MaxPowerCount, powerCount);
                            MaxPowerCounts[level - 1] = Math.Max(MaxPowerCounts[level - 1], powerCount);
                        }
                    }

                    // bail if stopped
                    if (_state != RunState.Running) break;

                    if (level % 10 == 0)
                    {
                        if (Updated != null) Updated(this, EventArgs.Empty);
                    }
                    */
                }

                generated++;
                Console.WriteLine(@"generated " + generated);
            }
        }

        private enum RunState
        {
            NotStarted,
            Running,
            Stopped
        }


        private readonly Dictionary<ItemType, ItemStat> _items = new Dictionary<ItemType, ItemStat>();
        
        private readonly Dictionary<PowerType, PowerStat> _powers = new Dictionary<PowerType, PowerStat>();
        private readonly AverageStat _monsterHealth;

        private RunState _state = RunState.NotStarted;
    }

    public abstract class StatBase : IStatRow
    {
        public bool IsTall => false;

        protected StatBase(AsyncStats stats)
        {
            Stats = stats;
            _counts = new int[100];
        }

        public int Increment(int level)
        {
            return ++_counts[level - 1];
        }

        #region IStatRow Members

        public abstract string Name { get; }

        public abstract TermColor Color { get; }

        public abstract int Max(int x);

        public IEnumerable<int> Values => _counts;

        #endregion

        protected AsyncStats Stats;
        private readonly int[] _counts;
    }

    public class RaceStat : StatBase
    {
        public RaceStat(AsyncStats stats, Race race)
            : base(stats)
        {
            _race = race;
        }

        public override string Name => _race.Name;

        public override int Max(int x) { return Stats.MaxPerLevel ? Stats.MaxRaceCounts[x] : Stats.MaxRaceCount; }

        public override TermColor Color
        {
            get
            {
                Character character = (Character)_race.Appearance;
                return character.ForeColor;
            }
        }

        private readonly Race _race;
    }

    public class ItemStat : StatBase
    {
        public ItemStat(AsyncStats stats, ItemType type)
            : base(stats)
        {
            _type = type;
        }

        public override string Name => _type.Name;

        public override int Max(int x) { return Stats.MaxPerLevel ? Stats.MaxItemCounts[x] : Stats.MaxItemCount; }

        public override TermColor Color
        {
            get
            {
                Character character = (Character)_type.Appearance;
                return character.ForeColor;
            }
        }

        private readonly ItemType _type;
    }

    public class PowerStat : StatBase
    {
        public PowerStat(AsyncStats stats, PowerType type)
            : base(stats)
        {
            _type = type;
        }

        public override string Name => _type.Name;

        public override int Max(int x) { return Stats.MaxPerLevel ? Stats.MaxPowerCounts[x] : Stats.MaxPowerCount; }

        public override TermColor Color
        {
            get
            {
                if (_type.Appearance != null)
                {
                    return (TermColor)_type.Appearance;
                }

                return TermColor.White;
            }
        }

        private readonly PowerType _type;
    }

    public class AverageStat : IStatRow
    {
        public bool IsTall => true;

        public IEnumerable<int> Values => _values;

        public AverageStat(string name)
        {
            Name = name;
            _values = new int[100];
        }

        public string Name { get; }

        public int Max(int x) { return _values.Max(); }

        public TermColor Color => TermColor.White;

        public void Add(int level, int amount)
        {
            _values[level - 1] += amount;
        }

        private readonly int[] _values;
    }
}
